from datetime import datetime, timedelta

from django.db.models import Q
from django.utils import timezone
from rest_framework.exceptions import ValidationError

from apps.booking.models import Appointment
from apps.tenant.models import ProviderLocation


def validate_appointment(request, attrs):
    ONE_HOUR_SECONDS = 3600
    FIFTY_MINUTE_SECONDS = 900

    start_time = None
    end_time = None
    clients = None
    # weekly_repeat = False
    start_date = None
    select_dates = False
    booking = None

    if 'booking' in attrs:
        booking = attrs['booking']
    if 'start_date' in attrs:
        start_date = attrs['start_date']

    if start_date is None and booking is None:
        raise ValidationError('Start Date must be provided')

    if start_date is None and booking:
        start_date = booking.start_date

    if 'start_time' in attrs:
        start_time = attrs['start_time']

    if 'end_time' in attrs:
        end_time = attrs['end_time']

    if 'clients' in attrs:
        clients = attrs['clients']

    # if 'weekly_repeat' in attrs:
    #     weekly_repeat = attrs.get('weekly_repeat', False)

    if 'select_dates' in attrs:
        select_dates = attrs.get('select_dates', False)

    # All the validations of booking are applied here too because of non-ethera appointment
    if start_time and end_time:
        start_time = datetime.strptime(start_time, '%H:%M:%S').time()
        end_time = datetime.strptime(end_time, '%H:%M:%S').time()
        if start_time > end_time:
            raise ValidationError(
                "Start time must be lower than end time.")
        start_datetime = timezone.make_aware(
            (datetime.combine(start_date.date(), start_time)),
            timezone.get_current_timezone())
        end_datetime = timezone.make_aware(
            (datetime.combine(start_date.date(), end_time)),
            timezone.get_current_timezone())

        duration = (end_datetime - start_datetime).total_seconds()
        if start_datetime < timezone.now():
            raise ValidationError({"msg": "Can not book in the past"})
        thirty_days = timezone.now() + timedelta(days=60)
        if start_datetime >= thirty_days:
            raise ValidationError({"msg": "Start Date must be less than Sixty days"})
        if duration < ONE_HOUR_SECONDS:
            raise ValidationError({"msg": "Duration must be at least one hour"})
        if duration % FIFTY_MINUTE_SECONDS != 0:
            raise ValidationError({"msg": "Booking duration must be 15-minutes increment"})
        if booking:
            validate_time_in_booking(attrs['booking'], start_time, end_time)

            if clients:
                validate_client_in_booking(
                    attrs['booking'], clients, start_time, end_time)

        # elif weekly_repeat:
        #     if 'number_of_weeks' not in attrs:
        #         raise ValidationError({'msg': 'number of weeks must be provided'})
        #     validate_provider_in_simple_appointment(self.context['request'].user, start_time, end_time,
        #                                             start_date)
        #     if clients:
        #         validate_client_in_simple_appointment(clients, start_time, end_time, start_date)

        elif select_dates:
            repeated_dates = attrs['selected_dates']
            validate_provider_in_repeating_appointment(request.user, start_time, end_time,
                                                       repeated_dates)
            if clients:
                validate_client_in_booking_repeat(clients, start_time, end_time, repeated_dates)

        else:
            validate_provider_in_simple_appointment(request.user, start_time, end_time, start_date)
            if clients:
                validate_client_in_simple_appointment(clients, start_time, end_time, start_date)

    if 'provider_location' in attrs and len(attrs['provider_location']) <= 0:
        attrs.pop('provider_location')
    else:
        provider_location = ProviderLocation.objects.filter(id=attrs['provider_location']).first()
        if provider_location:
            attrs['provider_location'] = provider_location
        else:
            raise ValidationError({"msg": "Provider Location does not exist"})

    return attrs


def validate_time_in_booking(booking, start_time, end_time):
    booking_start_time = booking.start_time
    booking_end_time = booking.end_time

    if start_time < booking_start_time or end_time > booking_end_time:
        raise ValidationError({"msg": "Appointment range time is out of booking."})


def validate_client_in_booking(booking, client, start_time, end_time, instance=None):
    range_time = (start_time, end_time)
    query = Appointment.objects.filter(booking=booking.pk, clients__in=client)
    query = query.filter(
        Q(start_time__range=range_time) |
        Q(end_time__range=range_time))
    if instance:
        query = query.exclude(id=instance.id)
    if query.count() > 0:
        raise ValidationError({"msg": "This time range has already existed with this client."})


def validate_client_in_booking_repeat(client, start_time, end_time, repeated_dates):
    range_time = (start_time, end_time)
    query = Appointment.objects.filter(clients__in=client, start_date__date__in=repeated_dates)

    query = query.filter(Q(start_time__range=range_time) |
                         Q(end_time__range=range_time))
    if query.count() > 0:
        raise ValidationError({'msg': 'Client is already in this booking'})


def validate_provider_in_repeating_appointment(provider, start_time, end_time, repeated_dates):
    range_time = (start_time, end_time)
    query = Appointment.objects.filter(start_date__date__in=repeated_dates)
    query = query.filter(Q(start_time__range=range_time) |
                         Q(end_time__range=range_time))
    if query.count() > 0:
        raise ValidationError(
            'Provider is already in this booking'
        )


def validate_client_in_simple_appointment(client, start_time, end_time, date, instance=None):
    range_time = (start_time, end_time)
    query = Appointment.objects.filter(clients__in=client, start_date__date=date)
    query = query.filter(Q(start_time__range=range_time) |
                         Q(end_time__range=range_time))
    if instance:
        query = query.exclude(id=instance.id)

    if query.count() > 0:
        raise ValidationError(
            'Client is already in this booking'
        )


def validate_provider_in_simple_appointment(provider, start_time, end_time, date):
    range_time = (start_time, end_time)
    query = Appointment.objects.filter(start_date__date=date)
    query = query.filter(Q(start_time__range=range_time) |
                         Q(end_time__range=range_time))

    query = query.exclude(
        Q(end_time=start_time) |
        Q(start_time=end_time)
    )
    if query.count() > 0:
        raise ValidationError(
            'Provider is already in this booking'
        )
